package com.gzz.orchid.common.webmvc.securityjwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * iss: jwt签发者
 * sub: jwt所面向的用户
 * aud: 接收jwt的一方
 * exp: jwt的过期时间，这个过期时间必须要大于签发时间
 * nbf: 定义在什么时间之前，该jwt都是不可用的.
 * iat: jwt的签发时间
 * jti: jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击。
 *
 * @author higmd
 */
@Slf4j
public class JwtUtil {
    //  token过期时间
    private static final Long EXP_TIME_HOURS = 48L;
    private static final String SECRET_KEY = "your_secret_key";
    private static final String ISSUER = "Gm@g-zz' Studio";
    private static final String SUBJECT = "Facade Data";

    private static final Algorithm ALGORITHM = Algorithm.HMAC256(SECRET_KEY);

    /**
     * 生成token
     */
    public static String generateToken(Map<String, String> extraClaims) {
        Map<String, Object> payload = Map.of("string-claim", "string-value", "number-claim", 42, "bool-claim", true, "datetime-claim", Instant.now());
        JWTCreator.Builder builder = JWT.create();
        extraClaims.forEach(builder::withClaim);
        return builder
                .withJWTId(UUID.randomUUID().toString())
                .withHeader(Map.of("alg", "HS256", "typ", "JWT"))
                .withPayload(payload)
                .withSubject(SUBJECT)
                .withIssuer(ISSUER)
                .withExpiresAt(Instant.now().atZone(ZoneId.of("Asia/Shanghai")).plusHours(EXP_TIME_HOURS).toInstant())
                .withIssuedAt(Instant.now())
                .sign(ALGORITHM);
    }

    /**
     * 解析token
     */
    public static DecodedJWT parseJWT(String token) {
        JWTVerifier verifier = JWT.require(ALGORITHM)
                .withIssuer(ISSUER)
                //                .withClaim("number-claim", 123)
                //                .withClaimPresence("some-claim-that-just-needs-to-be-present")
                //                .withClaim("predicate-claim", (claim, decodedJWT) -> "custom value".equals(claim.asString()))
                .build();
        return verifier.verify(token);
    }

    /**
     * 获取token中的Claim extractClaim
     */
    public static Optional<Map<String, String>> getClaims(String token) {
        return Optional.of(parseJWT(token)
                .getClaims()
                .entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().asString())));
    }

    /**
     * 校验token
     */
    public static boolean validateToken(String token) {
        try {
            parseJWT(token);
        } catch (JWTVerificationException e) {
            log.error("Token[{}]无法通过验证! {} ", token, e.getMessage());
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * 获取token的过期时间
     */
    public static Instant getExpiresAt(String token) {
        return JWT.decode(token).getExpiresAtAsInstant();
    }

    /**
     * 签发Rs256Token
     */
    public static String creatTokenByRs256(Map<String, Object> data) throws NoSuchAlgorithmException {
        //初始化 公钥/私钥
        RSA256Key rsa256Key = RSA256Util.generateRSA256Key();

        //加密时，使用私钥生成RS算法对象
        Algorithm algorithm = Algorithm.RSA256(rsa256Key.getPrivateKey());

        return JWT.create()
                //签发人
                .withIssuer(ISSUER)
                //接收者
                .withAudience(data.toString())
                //签发时间
                .withIssuedAt(Instant.now())
                //过期时间
                .withExpiresAt(Instant.now().atZone(ZoneId.of("Asia/Shanghai")).plusDays(1).toInstant())
                //相关信息
                .withClaim("data", data)
                //签入
                .sign(algorithm);
    }

    /**
     * 验证Token
     */
    public static boolean verifyRs256Token(String token) throws NoSuchAlgorithmException {

        //获取公钥/私钥
        RSA256Key rsa256Key = RSA256Util.generateRSA256Key();

        //根据密钥对生成RS256算法对象
        Algorithm algorithm = Algorithm.RSA256(rsa256Key.getPublicKey());

        System.out.println("PublicKey: " + rsa256Key.getPublicKey().getPublicExponent());

        //解密时，使用gong钥生成算法对象
        JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer(ISSUER)
                .build();

        try {
            //验证Token，verifier自动验证
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            log.error("Token无法通过验证! " + e.getMessage());
            return false;
        }
    }


    /**
     * 获取用户信息
     */
    public static UserDetails getUserDetails(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            try {
                DecodedJWT jwt = parseJWT(token);
                Map<String, Claim> claims = jwt.getClaims();
                Date exp = claims.get("exp").asDate();
                if (exp.before(new Date())) {
                    throw new RuntimeException("Token已过期!");
                }
                return User.withUsername(claims.get("username").asString())
                        //.password(claims.get("password").asString())
                        .authorities(claims.get("authorities").asArray(String.class))
                        .build();
            } catch (Exception e) {
                log.error("Token无法通过验证! {}", e.getMessage());
                // e.printStackTrace();
                throw new RuntimeException("Tocken验证失败! ");
            }
        }
        return null;
    }

    /**
     * 从请求头中提取token
     *
     */
    public static String extractToken(HttpServletRequest request) {

        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            //return bearerToken.replaceAll("Bearer\s+", "");
            return bearerToken.substring(7);
        }
        return null;
    }
}